#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <map>
#include <set>
#include <ostream>
#include <algorithm>

using std::ostream;
using std::cout;
using std::cerr;
using std::endl;
using std::string;
using std::ifstream;
using std::istringstream;
using std::map;
using std::set;
using std::vector;

class QueryResult;

class TextQuery
{
public:
    void readFile(const string& filename);
    QueryResult query(const string& word);
    vector<string>& getVector();

private:
    vector<string> _lines;
    map<string, set<int>> _wordNumbers;
    map<string, int> _dict;
};

class QueryResult
{
public:
    QueryResult() = default;
    QueryResult(string word, set<int>& idx, vector<string>& lines)
        : _word(word), _idx_line(idx), _lines(lines) {}

    set<int>::iterator begin() const {
        return _idx_line.begin();
    }

    set<int>::iterator end() const {
        return _idx_line.end();
    }

    set<int>::size_type size() const {
        return _idx_line.size();
    }

    friend ostream& operator<<(ostream& os, const QueryResult& qResult);

private:
    string _word;
    set<int> _idx_line;
    vector<string> _lines;
};

ostream& operator<<(ostream& os, const QueryResult& qResult) {
    os << "The word: " << qResult._word << " occurs: " << qResult.size() << " times." << endl;
    for (auto it_set = qResult.begin(); it_set != qResult.end(); ++it_set) {
        int rowNumber = *it_set;
        if (rowNumber - 1 >= 0 && rowNumber - 1 < qResult._lines.size()) {
            os << "(line " << rowNumber << ")";
            os << qResult._lines[rowNumber - 1] << endl;
        }
    }
    return os;
}

void TextQuery::readFile(const string& filename) {
    ifstream ifs(filename);
    if (!ifs.good()) {
        cerr << "fail to open file" << endl;
        return;
    }
    int lineNumber = 0;
    string line;
    while (getline(ifs, line)) {
        ++lineNumber;
        _lines.push_back(line);
        istringstream iss(line);
        string word;
        while (iss >> word) {
            bool hasDigit = false;
            for (auto& ch : word) {
                if (isdigit(ch)) {
                    hasDigit = true;
                    break;
                }
            }
            if (hasDigit) {
                continue;
            }
            if (ispunct(word.back())) {
                continue;
            }
            if (ispunct(word.front())) {
                continue;
            }
            if (isupper(word[0])) {
                word[0] = tolower(word[0]);
            }
            ++_dict[word];
            _wordNumbers[word].insert(lineNumber);
        }
    }
    ifs.close();
}

QueryResult TextQuery::query(const string& word) {
    auto it_word = _wordNumbers.find(word);
    if (it_word != _wordNumbers.end()) {
        set<int>& idx_line = it_word->second;
        QueryResult qResult(word, idx_line, _lines);
        return qResult;
    } else {
        set<int> emptySet;
        QueryResult qResult(word, emptySet, _lines);
        return qResult;
    }
}

vector<string>& TextQuery::getVector() {
    return _lines;
}

class AndQueryResult {
public:
    QueryResult read(const string& filename, const string& lhs, const string& rhs);
};

QueryResult AndQueryResult::read(const string& filename, const string& lhs, const string& rhs) {
    TextQuery textQuery;
    textQuery.readFile(filename);
    QueryResult qleft = textQuery.query(lhs);
    QueryResult qright = textQuery.query(rhs);
    set<int> lines;
    std::set_intersection(qleft.begin(), qleft.end(), qright.begin(),
        qright.end(), std::inserter(lines, lines.end()));
    string temp = lhs + ' ' + rhs;
    QueryResult tmp(temp, lines, textQuery.getVector());
    return tmp;
}

int main(int argc, char* argv[]) {
    string queryWord("the");
    string queryWord2("link");
    AndQueryResult andQueryResult;
    QueryResult result = andQueryResult.read("china_daily.txt", queryWord, queryWord2);
    cout << result << endl;

    return 0;
}